package com.aididi.concurrent;

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @Auther: zx.xu
 * @Description:    可重入锁
 * Java的可重入锁有：reentrantLock（显式的可重入锁）、synchronized（隐式的可重入锁）
 * 可重入锁诞生的目的就是防止死锁，导致同一个线程不可重入上锁代码段，目的就是让同一个线程可以重新进入上锁代码段。
 *
 * 1）不可重入锁：
            当线程在访问A方法的时候，获取的A方法的锁，在A方法锁释放之前不能够访问其他方法（如方法B）的锁。
    2）可重入锁：
        当线程在访问A方法的时候，获取A方法的锁，然后访问B方法获取B方法的锁，并计数加1，以此类推可以访问完了以后依次解锁。
    可重入锁模型：｛｛｛｛｝｝｝｝ 每次都可访问另一个方法，且加锁计数器加1，完全释放锁为计数器等于0
 */
public class reentrantLockDemo2 {

    //可重入锁，是指同一个线程可以重入上锁的代码段，不同的线程进入则需要进行阻塞等待
    public static void main(String[] args) throws InterruptedException {
        //synchronized可重入锁
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (this) {
                    System.out.println("第1次获取锁，这个锁是：" + this);
                    int index = 1;
                    while (true) {
                        synchronized (this) {
                            System.out.println("第" + (++index) + "次获取锁，这个锁是：" + this);
                        }
                        if (index == 10) {
                            break;
                        }
                    }
                }
            }
        }).start();

        //ReentrantLock演示可重入锁是什么意思
        ReentrantLock lock = new ReentrantLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    System.out.println("第1次获取锁，这个锁是：" + lock);

                    int index = 1;
                    while (true) {
                        try {
                            lock.lock();
                            System.out.println("第" + (++index) + "次获取锁，这个锁是：" + lock);

                            try {
                                Thread.sleep(new Random().nextInt(200));
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }

                            if (index == 10) {
                                break;
                            }
                        } finally {
                            lock.unlock();
                        }

                    }

                } finally {
                    lock.unlock();
                }
            }
        }).start();

        //测试不可重入锁
        /**
         * 控制台会打印“执行doJob方法前”,然后就会一直线程阻塞，不会打印 "执行doJob方法过程中"原因在于第一次上锁后，
         * 由于没有释放锁，因此执行第一次lock后isLocked = true，这个时候调用doJob()内部又一次调用了lock()，
         * 由于上个线程将isLocked = true，导致再次进入的时候就进入死循环。
         * 从而导致线程无法执行System.out.println("执行doJob方法过程中"); 这行代码，
         * 因此控制台只能打印 "执行doJob方法前"。这种现象就造成了不可重入锁
         */
        new reentrantLockDemo2().doSomeThing();
    }

    //演示不可重入锁实例代码
    MyLock lock = new MyLock();
    public void doSomeThing() throws InterruptedException {
        lock.lock(); // 第一次上锁
        System.out.println("执行doJob方法前");
        doJob(); // 方法内会再次上锁
        lock.unlock(); // 释放第一次上的锁
    }
    public void doJob() throws InterruptedException {
        lock.lock();
        System.out.println("执行doJob方法过程中");
        lock.unlock();
    }


    //可重入锁的实例代码
    boolean isLocked = false;   // 默认没有上锁
    Thread lockedBy = null; // 记录阻塞线程
    int lockedCount = 0;    // 上锁次数计数

    /**
     * 上锁逻辑
     */
    public synchronized void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();
        // 上锁了 并且 如果是同一个线程则放行，否则其它线程需要进入where循环进行等待
        while (isLocked && lockedBy != thread) {
            wait();
        }
        isLocked = true; // 第一次进入就进行上锁
        lockedCount++; // 上锁次数计数
        lockedBy = thread; // 当前阻塞的线程
    }

    /**
     * 释放锁逻辑
     */
    public synchronized void unlock() {
        if (Thread.currentThread() == this.lockedBy) {
            lockedCount--; // 将上锁次数减一
            if (lockedCount == 0) {// 当计数为0，说明所有线程都释放了锁
                isLocked = false; // 真正的将释放了所有锁
                notify();
            }
        }
    }


}


/**
 * 自定义锁(不可重入锁)
 */
class MyLock{
    private boolean isLocked = false;
    public synchronized void lock() throws InterruptedException{
        while(isLocked){
            wait();
        }
        isLocked = true; // 线程第一次进入后就会将器设置为true，第二次进入是就会由于where true进入死循环
    }
    public synchronized void unlock(){
        isLocked = false;   // 将这个值设置为false目的是释放锁
        notify();           // 结束阻塞
    }
}
